package com.recognition.image;

import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

import javax.imageio.ImageIO;

import com.recognition.Logger;
import com.recognition.swing.JImagePanel;

public class SnapshotManager
{
	private static Logger _log = Logger.getLogger();
	
	private static SnapshotManager manager;
	
	private JImagePanel _refPan, _snapPan, _resPan;
	
	private HashMap<Integer, BufferedImage> _results;
	private HashMap<Integer, BufferedImage> _snapshots;
	private HashMap<Integer, BufferedImage> _references;
	private HashMap<Integer, String> _names;
	private HashMap<Integer, Float[][]> _tables;
	private HashMap<Integer, Integer[]> _positions;
	
	private BufferedImage _result;
	private BufferedImage _img;
	private BufferedImage _ref;
	private String _name;
	private int _outputUnit;
	private int[][][] _matrixRef;
	private Float[][] _table;
	private int height, width;
	private int firstX, firstY;

	public SnapshotManager(JImagePanel ref, JImagePanel snap, JImagePanel res)
	{
		_refPan = ref;
		_snapPan = snap;
		_resPan = res;
		manager = this;
		
		_results = new HashMap<Integer, BufferedImage>();
		_snapshots = new HashMap<Integer, BufferedImage>();
		_references = new HashMap<Integer, BufferedImage>();
		_names = new HashMap<Integer, String>();
		_tables = new HashMap<Integer, Float[][]>();
		_positions = new HashMap<Integer, Integer[]>();
	}
	
	public void reinitialize()
	{
		_results.clear();
		_snapshots.clear();
		_references.clear();
		_names.clear();
		_tables.clear();
		_positions.clear();
	}
	
	public static SnapshotManager getManager()
	{
		return manager;
	}
	
	public void setImage(BufferedImage img)
	{
		_img = img;	
		
		if (img != null)
		{
			firstX = 999999;
			firstY = 999999;
			height = img.getHeight();
			width = img.getWidth();
			_result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
			_table = new Float[width][height];
			for (int i = 0; i < height; ++i)
			{			
				for (int j = 0; j < width; ++j)
				{
					int c = img.getRGB(j, i);
					int d1 = _matrixRef[j][i][0] - ((c & 0x00ff0000) >> 16);
					int d2 = _matrixRef[j][i][1] - ((c & 0x0000ff00) >> 8);
					int d3 = _matrixRef[j][i][2] - (c & 0x000000ff);
					
					/*
					if (d1 < 0)
						d1 = 255-d1;
					if (d2 < 0)
						d2 = 255-d2;
					if (d3 < 0)
						d3 = 255-d3;
					
					_result.setRGB(j, i, d3 + (d2 << 8) + (d3 << 16));
					*/
					
					if (d1 < 0 && d2 < 0 && d3 < 0 && Math.abs(2*d1 - d2 - d3) < 5)
					{
						_table[j][i] = (float) 0;
						_result.setRGB(j,i,0xffffff);
					}
					else if (d1 > 0 && d2 > 0 && d3 > 0 && Math.abs(2*d1 - d2 - d3) < 5)
					{
						_table[j][i] = (float) 0;
						_result.setRGB(j,i,0xffffff);
					}
					else if (Math.abs(d1) + Math.abs(d2) + Math.abs(d3) > 30)
					{
						_table[j][i] = (float) 1;
						_result.setRGB(j,i,0);
					}
					else
					{
						_table[j][i] = (float) 0;
						_result.setRGB(j,i,0xffffff);
					}
					
				}
			}					
		}	
		
		filterSinglePixels(3);	
		
		if (firstX > width - 51)
			firstX = width - 51;
		
		if (firstY > height - 51)
			firstY = height - 51;
		
		for (int i = firstY; i < firstY + 50; i++)
		{
			_result.setRGB(firstX,i,0xff0000);
			_result.setRGB(firstX + 50,i,0xff0000);
		}
		
		for (int i = firstX; i < firstX + 50; i++)
		{
			_result.setRGB(i,firstY,0xff0000);
			_result.setRGB(i,firstY + 50,0xff0000);
		}	
		
		_snapPan.setImage(_img);		
		_resPan.setImage(_result);		
		_resPan.drawRedBox(firstX,firstY);
	}
	
	public void filterSinglePixels(int minNeighbours)
	{
		if (_table[0][0] != 0 && _table[0][1] + _table[1][1] + _table[1][0] < minNeighbours)
		{
			_table[0][0] = (float) 0;
			_result.setRGB(0,0,0xffffff);
		}
		
		if (_table[0][height - 1] != 0 && _table[0][height - 2] + _table[1][height - 2] + _table[1][height - 1] < minNeighbours)
		{
			_table[0][height - 1] = (float) 0;
			_result.setRGB(0,height - 1,0xffffff);
		}
		
		if (_table[width - 1][0] != 0 && _table[width - 2][0] + _table[width - 2][1] + _table[width - 1][1] < minNeighbours)
		{
			_table[width - 1][0] = (float) 0;
			_result.setRGB(width - 1,0,0xffffff);
		}
		
		if (_table[width - 1][height - 1] != 0 && _table[width - 2][height - 1] + _table[width - 2][height - 2] + _table[width - 1][height - 2] < minNeighbours)
		{
			_table[width - 1][height - 1] = (float) 0;
			_result.setRGB(width - 1,height - 1,0xffffff);
		}
		
		for (int i = 1; i < height - 1; ++i)
		{
			if (_table[0][i] != 0 && _table[0][i-1] + _table[0][i+1] + _table[1][i-1] + _table[1][i] + _table[1][i+1] < minNeighbours)
			{
				_table[0][i] = (float) 0;
				_result.setRGB(0,i,0xffffff);
			}
		}
		
		for (int i = 1; i < width - 1; ++i)
		{
			if (_table[i][0] != 0 && _table[i-1][0] + _table[i+1][0] + _table[i-1][1] + _table[i][1] + _table[i+1][1] < minNeighbours)
			{
				_table[i][0] = (float) 0;
				_result.setRGB(i,0,0xffffff);
			}
		}
		
		for (int i = 1; i < height - 1; ++i)
		{			
			for (int j = 1; j < width - 1; ++j)
			{
				if (_table[j][i] != 0)
				{
					float k = _table[j-1][i-1] + _table[j][i-1] + _table[j+1][i-1] + _table[j-1][i] 
							+ _table[j+1][i] + _table[j-1][i+1] + _table[j][i+1] + _table[j+1][i+1];
					if (k < minNeighbours)
					{
						_table[j][i] = (float) 0;
						_result.setRGB(j,i,0xffffff);
					}
					else if (k > 5)
					{
						if (firstX > j)
						{
							firstX = j - 1;
						}
						if (firstY > i)
						{
							firstY = i - 1;
						}
					}					
				}
				
				if (_table[j][i] != 0 && _table[j-1][i-1] + _table[j][i-1] + _table[j+1][i-1] + _table[j-1][i] 
						+ _table[j+1][i] + _table[j-1][i+1] + _table[j][i+1] + _table[j+1][i+1] < minNeighbours)
				{
					_table[j][i] = (float) 0;
					_result.setRGB(j,i,0xffffff);
				}
			}
		}	
	}
	
	public void setReference(BufferedImage ref)
	{
		_ref = ref;		
		
		_refPan.setImage(_ref);
		
		if (ref != null)
		{
			height = ref.getHeight();
			width = ref.getWidth();
			_matrixRef = new int[width][height][3];
			for (int i = 0; i < height; i++)
			{			
				for (int j = 0; j < width; j++)
				{
					int c = ref.getRGB(j, i);
					_matrixRef[j][i][0] = (c & 0x00ff0000) >> 16;
					_matrixRef[j][i][1] = (c & 0x0000ff00) >> 8;
					_matrixRef[j][i][2] = (c & 0x000000ff);
				}
			}
		}		
	}
	
	public BufferedImage getReference()
	{
		return _ref;
	}
	
	public BufferedImage getSnapshot()
	{
		return _img;
	}
	
	public BufferedImage getResult()
	{
		return _result;
	}
	
	public Set<Integer> getKeys()
	{
		return _results.keySet();
	}
	
	public boolean saveSnapshot(int unit, int trial, String name)
	{		
		if (_result == null || _img == null || _ref == null)
			return false;
		
		int idx = (unit << 8) + trial;
		
		_results.put(idx, _result);
		_snapshots.put(idx, _img);
		_references.put(idx, _ref);
		_names.put(unit, name);
		_tables.put(idx, _table);	
		
		_positions.put(idx, new Integer[]{firstX,firstY});
		
		return true;
	}
	
	public boolean loadSnapshot(int unit, int trial)
	{		
		int idx = (unit << 8) + trial;
		
		if (!_results.containsKey(idx))
			return false;
		
		_result = _results.get(idx);
		_img = _snapshots.get(idx);
		_ref = _references.get(idx);
		_name = _names.get(unit);
		_table = _tables.get(idx);
		
		Integer[] tmp = _positions.get(idx);
		firstX = tmp[0];
		firstY = tmp[1];	
		
		_resPan.setImage(_result);
		_snapPan.setImage(_img);
		_refPan.setImage(_ref);
		
		return true;
	}

	public boolean deleteSnapshot(int unit, int trial) 
	{
		int idx = (unit << 8) + trial;
		
		if (!_names.containsKey(idx))
			return false;
		
		_results.remove(idx);
		_snapshots.remove(idx);
		_references.remove(idx);
		_names.remove(unit);
		_tables.remove(idx);
		_positions.remove(idx);
		
		return true;
	}
	
	public Float[] getCurrentTable()
	{
		Float[] tmp = new Float[2500];
		
		for (int i = 0; i < 50; i++)
			System.arraycopy(_table[firstX+i], firstY, tmp, 50 * i, 50);
		
		return tmp;
	}
	
	public Float[] getTable(int idx)
	{		
		Float[] tmp = new Float[2500];
		Float[][] table = _tables.get(idx);
		Integer pos[] = _positions.get(idx);
		
		for (int i = 0; i < 50; i++)
			System.arraycopy(table[pos[0]+i], pos[1], tmp, 50 * i, 50);
		
		return tmp;
	}
	
	public String getCurrentName()
	{
		return _name;
	}
	
	public String getName(int idx)
	{
		idx =  ((idx & 0xff00) >> 8);
		return _names.get(idx);
	}
	
	public void exportAll(String dir, String imageDir, String tableDir)
	{
		String file = dir + "/project.cfg";
		FileOutputStream stream;
		BufferedWriter writer;
		try {
			stream = new FileOutputStream(new File(file));
			writer = new BufferedWriter(new OutputStreamWriter(stream));
			
			for (Integer key : _results.keySet())
			{
				String partName = "/" + getName(key) + "_" + ((key & 0xff));
				String name = imageDir + partName;
				
				FileOutputStream fos = new FileOutputStream(name + "_result.jpg");
				ImageIO.write(_results.get(key), "JPG", fos);
				fos.close();
				fos = new FileOutputStream(name + "_snapshot.jpg");
				ImageIO.write(_snapshots.get(key), "JPG", fos);
				fos.close();
				fos = new FileOutputStream(name + "_reference.jpg");
				ImageIO.write(_references.get(key), "JPG", fos);
				fos.close();	
				
				writer.write(key + ";");
				writer.write(partName + ";");
				Integer[] pos = _positions.get(key);
				writer.write(pos[0] + ";" + pos[1] + ";");
				writer.newLine();	
				
				Float[][] table = _tables.get(key);
				
				if (table == null)
					continue;
				
				FileOutputStream stream2 = new FileOutputStream(new File(tableDir + partName + ".tbl"));
				BufferedWriter writer2 = new BufferedWriter(new OutputStreamWriter(stream2));
				writer2.write(table.length + ";" + table[0].length);
				writer2.newLine();
				for (int j = 0; j < table[0].length; ++j)
				{
					for (int i = 0; i < table.length; ++i)
						writer2.write(table[i][j] + ";");		
					
					writer2.newLine();
				}
				writer2.close();
				stream2.close();
			}
			
			writer.close();
			stream.close();
		} catch (FileNotFoundException e) {
			_log.error("Saving project: file not found");
			_log.error("Saving project: " + e.getMessage());
		} catch (IOException e) {
			_log.error("Saving project: failed to save the project");
		}	
	}	
	
	public void importAll(String cfg, String dir)
	{
		reinitialize();
		
		InputStream in;
		try {
			in = new FileInputStream(new File(cfg));
			BufferedReader dis  = new BufferedReader(new InputStreamReader(in));
			
			String line;
			while ((line = dis.readLine()) != null)
			{				
				String[] params = line.split(";");
				if (params.length < 4)
				{
					_log.error("Loading project: Invalid line in project.cfg");
					_log.error("Loading project: " + line);
					continue;
				}
				
				int key = Integer.parseInt(params[0]);
				String file = params[1];
				int x = Integer.parseInt(params[2]);
				int y = Integer.parseInt(params[3]);
				
				_positions.put(key, new Integer[]{x,y});
				
				params = file.split("_");
				if (params.length < 2)
				{
					_log.error("Loading project: Invalid line in project.cfg");
					_log.error("Loading project: " + file);
					continue;
				}
				
				String name = params[0].substring(1);
				if (!_names.containsValue(name))
					_names.put(((key & 0xff00) >> 8), name);				
				
				FileInputStream fis = new FileInputStream(dir + "/images" + file + "_result.jpg");
				_results.put(key, ImageIO.read(fis));
				fis.close();
				fis = new FileInputStream(dir + "/images" + file + "_snapshot.jpg");
				_snapshots.put(key, ImageIO.read(fis));
				fis.close();
				fis = new FileInputStream(dir + "/images" + file + "_reference.jpg");
				_references.put(key, ImageIO.read(fis));
				fis.close();
				
				FileInputStream stream2 = new FileInputStream(new File(dir + "/tables" + file + ".tbl"));
				BufferedReader reader2 = new BufferedReader(new InputStreamReader(stream2));
				if (!reader2.ready())
					continue;
				
				String line2;
				String[] dim = reader2.readLine().split(";");
				int width = Integer.parseInt(dim[0]);
				int height = Integer.parseInt(dim[1]);
				Float[][] table = new Float[width][height];
				
				int j = 0;
				while ((line = reader2.readLine()) != null)
				{
					if (j == height)
						break;
					
					String[] row = line.split(";");
					
					if (row.length < width)
					{
						_log.error("Loading project: too small row, length is " + row.length + " and should be " + (width - 1));
						continue;
					}
					
					for (int i = 0; i < row.length; ++i)
						table[i][j] = Float.parseFloat(row[i]);
					
					++j;
				}
		
				
				_tables.put(key, table);
				
				reader2.close();
				stream2.close();
			}
				
			dis.close();
			in.close();
		} catch (FileNotFoundException e) {
			_log.error("Loading project: " + e.getMessage());
		} catch (IOException e) {
			_log.error("Loading project: can't read project.cfg");
		}						
	}	
}
